#  This file is part of OpenLightingDesigner.
# 
#  OpenLightingDesigner is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  OpenLightingDesigner is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with OpenLightingDesigner.  If not, see <http://www.gnu.org/licenses/>.
#
# Universe.py
# Copyright (c) 2010 - 2011 Matthew Reid

import socket
import array
from Bases import setID
from Bases import OSCBaseObject

class Universe(OSCBaseObject):
    _saved_class_name = 'Universe'
    _saved_attributes = ['id']
    _Properties = {'name':dict(default=''),
                   'ola_universe_id':dict(type=int, ignore_type=True), 
                   'Artnet_Subnet':dict(type=int), 
                   'Artnet_Universe':dict(type=int), 
                   'merge_mode':dict(default='htp', entries=('htp', 'ltp'))}
    _SettingsProperties = ['name', 'Index', 'Artnet_Subnet', 'Artnet_Universe', 'merge_mode', 'ola_universe_id']
    def __init__(self, **kwargs):
        self.id = None
        self._ola_universe_id = None
        self.ola_enabled = False
        self.artnet_sequence = 1
        self.merged_universes = set()
        self.comm = kwargs.get('comm')
        prebind = kwargs.get('prebind', {})
        prebind.update({'ola_universe_id':self._on_ola_universe_id_set})
        kwargs['prebind'] = prebind
        super(Universe, self).__init__(**kwargs)
        #self.bind(ola_universe_id=self._on_ola_universe_id_set)
        self.register_signal('value_update', 'request_values')
        if 'deserialize' not in kwargs:
            self.id = setID(kwargs.get('id'))
            self.name = kwargs.get('name', '')
            self.ola_universe_id = kwargs.get('ola_universe_id')
            self.Artnet_Subnet = kwargs.get('Artnet_Subnet')
            self.Artnet_Universe = kwargs.get('Artnet_Universe')
            self.merge_mode = kwargs.get('merge_mode', 'htp')
        self._merge_func_map = {'htp':self._merge_func_htp, 'ltp':self._merge_func_ltp}
        self._merge_func = self._merge_func_map[self.merge_mode]
        self.values = array.array('B', [0]*513)
        self._internal_values = array.array('B', [0]*513)
        self.patched_channels = set()
        self.bind(merge_mode=self._on_merge_mode_set)
        self.root_address = 'OSCtoOLA-%s' % (socket.gethostname())
        self.add_osc_handler(callbacks={'request-dump':self.on_osc_request_dump})
        
    def add_attribute(self, attrib):
        self.patched_channels.add(attrib.chan)
        self.on_attribute_value_changed(obj=attrib, value=attrib.value)
        attrib.bind(value=self.on_attribute_value_changed)
        
    def del_attribute(self, attrib):
        self.patched_channels.discard(attrib.chan)
        attrib.unbind(self)
        
    def on_attribute_value_changed(self, **kwargs):
        attribute = kwargs.get('obj')
        chan = attribute.chan
        value = int(round(kwargs.get('value')))
        if chan < 1 or chan > 512:
            return
        if value > 255:
            value = 255
        elif value < 0:
            value = 0
        #self.values[chan-1] = value
        self._internal_values[chan-1] = value
        self._merge_internal(chan, value)
        if self.ola_enabled:
            self.osc_node.send_message(root_address=self.root_address, client=self.root_address, 
                                       address='set-channel', value=[chan, value])
        self.emit('value_update', channel=chan, value=value, universe=self)
        
    def _merge_internal(self, chan, value):
        if not len(self.merged_universes):
            self.values[chan-1] = value
            return
        return self._merge_func(chan, value, self)
        
    def add_merge_universe(self, univ):
        self.merged_universes.add(univ)
        univ.bind(value_update=self.on_merge_universe_value_update)
        
    def del_merge_universe(self, univ):
        univ.unbind(self)
        self.merged_universes.discard(univ)
        
    def on_merge_universe_value_update(self, **kwargs):
        chan = kwargs.get('channel')
        value = kwargs.get('value')
        #self.values[chan-1] = value
        univ = kwargs.get('universe')
        if univ:
            kwargs.setdefault('merge_source', univ)
        self.merge_input(**kwargs)
        
    def merge_input(self, **kwargs):
        values = kwargs.get('values')
        merge_source = kwargs.get('merge_source')
        #if merge_source not in self.merge_sources:
        #    self.merge_sources[merge_source] = array.array('B', [0]*513)
        if values is None:
            chan = kwargs.get('channel')
            value = kwargs.get('value')
            flag = self._merge_func(chan, value, merge_source)
            if flag:
                self.emit('value_update', universe=self, channel=chan, value=value)
        else:
            for chan, value in enumerate(values):
                flag = self._merge_func(chan+1, value, merge_source)
                if flag:
                    self.emit('value_update', universe=self, channel=chan+1, value=value)
                
    def _merge_func_htp(self, chan, value, merge_source):
        merged = [univ.values[chan-1] for univ in self.merged_universes]
        if not len(merged):
            merged = [0]
        highest = max(merged)
        #self.merge_sources[merge_source][chan] = value
        if value > highest:
            highest = value
        if highest > self._internal_values[chan-1]:
            flag = False
            if highest > self.values[chan-1]:
                flag = True
            self.values[chan-1] = highest
            return flag
        if highest < self.values[chan-1]:
            self.values[chan-1] = highest
            return True
            
    def _merge_func_ltp(self, chan, value, merge_source):
        if value != self.values[chan-1]:
            self.values[chan-1] = value
            return True
        
    def _on_merge_mode_set(self, **kwargs):
        self._merge_func = self._merge_func_map[self.merge_mode]
            
    def _on_ola_universe_id_set(self, **kwargs):
        value = kwargs.get('value')
        if value is not None:
            address = str(value)
            self._ola_universe_id = value
            self.ola_enabled = True
        else:
            address = self.id
            self.ola_enabled = False
        if 'ola_IO' not in self.comm.dmx_io:
            self.ola_enabled = False
        self.set_osc_address(address)
        
    def on_osc_request_dump(self, **kwargs):
        #client = kwargs.get('client')
        self.osc_node.send_message(root_address=self.root_address, client=self.root_address, 
                                   address='dump-response', value=list(self.values))
                        
class InUniverse(Universe):
    _saved_class_name = 'InputUniverse'
    _Properties = {'merge_universe':dict(default=1)}
    _SettingsProperties = ['merge_universe', ]
    def __init__(self, **kwargs):
        self.out_universes = kwargs.get('out_universes')
        self.merge_universe_obj = None
        super(InUniverse, self).__init__(**kwargs)
        if 'deserialize' not in kwargs:
            self.merge_universe = kwargs.get('merge_universe')
        self.attach_universe()
        self.out_universes.bind(child_update=self.on_out_universes_child_update)
        self.bind(merge_universe=self._on_merge_universe_set)
        self.bind(value_update=self.on_own_value_update)
    def on_own_value_update(self, **kwargs):
        print kwargs
    def merge_input(self, **kwargs):
        values = kwargs.get('values')
        merge_source = kwargs.get('merge_source')
        #if merge_source not in self.merge_sources:
        #    self.merge_sources[merge_source] = array.array('B', [0]*513)
        def do_merge(chan, value):
            flag = False
            if value != self._internal_values[chan-1]:
                flag = True
            self._internal_values[chan-1] = value
            self._merge_internal(chan, value)
            if flag:
                self.emit('value_update', universe=self, channel=chan, value=value)
        if values is None:
            chan = kwargs.get('channel')
            value = kwargs.get('value')
            do_merge(chan, value)
        else:
            for chan, value in enumerate(values):
                do_merge(chan+1, value)
                
    def attach_universe(self):
        i = self.merge_universe
        univ = self.out_universes.indexed_items.get(i)
        if univ == self.merge_universe_obj:
            return
        if self.merge_universe_obj is not None:
            self.merge_universe_obj.del_merge_universe(self)
        self.merge_universe_obj = univ
        if self.merge_universe_obj is not None:
            self.merge_universe_obj.add_merge_universe(self)
    def on_out_universes_child_update(self, **kwargs):
        self.attach_universe()
    def _on_merge_universe_set(self, **kwargs):
        self.attach_universe()
    
